/**
 * JarClassLoader.java
 * 
 * FreeZzaph is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * FreeZzaph is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.
 */
package freezzaph.classloader;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

public final class JarClassLoader extends MultiClassLoader {
	
	/**
	 * JarResources: JarResources maps all resources included in a Zip or Jar file.
	 * Additionaly, it provides a method to extract one as a blob.
	 * 
	 * @author FreeZzaph
	 */
	private final class JarResources {

		// jar resource mapping tables
		private Hashtable<String, Integer> sizes = new Hashtable<String, Integer>();
		private Hashtable<String, byte[]> jarContents =
				new Hashtable<String, byte[]>();

		private String jarFileName;

		/**
		 * Creates a JarResources instance. It extracts all resources from a jar
		 * into an internal hashtable, keyed by resource names.
		 * 
		 * @param jarFileName
		 *            a jar or zip file
		 */
		public JarResources(String jarFileName) {
			this.jarFileName = jarFileName;
			init();
		}

		/**
		 * Extracts a jar resource as a blob.
		 * 
		 * @param name
		 *            a resource name.
		 */
		public byte[] getResource(String name) {
			return jarContents.get(name);
		}

		/** initializes internal hash tables with Jar file resources. */
		private void init() {
			try {
				// Extracts sizes only.
				ZipFile zf = new ZipFile(jarFileName);
				Enumeration<?> e = zf.entries();
				while (e.hasMoreElements()) {
					ZipEntry ze = (ZipEntry)e.nextElement();

					sizes.put(ze.getName(), new Integer((int)ze.getSize()));
				}
				zf.close();

				// Extract resources and put them in the hashtable.
				FileInputStream fis = new FileInputStream(jarFileName);
				BufferedInputStream bis = new BufferedInputStream(fis);
				ZipInputStream zis = new ZipInputStream(bis);
				ZipEntry ze = null;
				while ((ze = zis.getNextEntry()) != null) {
					if (ze.isDirectory()) {
						continue;
					}

					int size = (int)ze.getSize();
					// -1 means unknown size.
					if (size == -1) {
						size = sizes.get(ze.getName()).intValue();
					}

					byte[] b = new byte[size];
					int rb = 0;
					int chunk = 0;
					while ((size - rb) > 0) {
						chunk = zis.read(b, rb, size - rb);
						if (chunk == -1) {
							break;
						}
						rb += chunk;
					}

					// Add to internal resource hashtable
					jarContents.put(ze.getName(), b);

				}
			} catch (NullPointerException e) { /**/
			} catch (FileNotFoundException e) { /**/
			} catch (IOException e) { /**/
			}
		}

	}

	private String jarName;
	private JarResources jarResources;

	public JarClassLoader(String jarName) {
		// Create the JarResource and suck in the jar file.
		this.jarName = jarName;
		jarResources = new JarResources(jarName);
	}

	@Override protected byte[] loadClassBytes(String className) {
		// Support the MultiClassLoader's class name munging facility.
		String formattedClassName = formatClassName(className);
		// Attempt to get the class data from the JarResource.
		return (jarResources.getResource(formattedClassName));
	}

	@Override protected URL findResource(String name) {
		try {
			return new URL("jar:file://" + jarName + "!/" + name);
		} catch (MalformedURLException e) {
			return null;
		}
	}
}
